package com.coolingme.springframework.web.servlet.v2;

import com.coolingme.springframework.web.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * 模拟Spring实现的核心功能类
 * <p>
 * v2版本
 *
 * @author wangyue
 * @date 2020/10/26 13:53
 */
public class MeDispatcherServlet extends HttpServlet {

    /**
     * IOC容器，注册式单例
     * <p>
     * 为了简化程序，暂时不考虑ConcurrentHashMap
     * <p>
     * 主要还是关注设计思想和原理
     */
    private Map<String, Object> ioc;

    /**
     * 保存application.properties 配置文件中的
     */
    private Properties contextConfig = new Properties();

    /**
     * 保存扫描的所有类名，统一执行实例化操作
     */
    private List<String> classNameList = new ArrayList<>();

    /**
     * 保存 url 和 方法的映射关系
     */
    private Map<String, Method> handlerMapping = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 根据URL找到对应方法并执行，委派模式
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Detail:" + Collections.singletonList(e.getMessage()));
        }
    }

    /**
     * 请求路径调度
     *
     * @param req  HttpServletRequest
     * @param resp HttpServletResponse
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        // uri
        String uri = req.getRequestURI();
        // 上下文环境（项目名）
        String contextPath = req.getContextPath();
        // 请求路径,去掉项目名和重复的 /
        String url = uri.replace(contextPath, "").replaceAll("/+", "/");

        // 网站图标，直接返回
        String favicon = "/favicon.ico";
        if (favicon.equals(url)) {
            return;
        }
        // 未找到对应的请求路径
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 not find!");
            return;
        }

        // 获取方法对象
        Method method = handlerMapping.get(url);

        // 1.参数动态获取
        // url参数Map
        Map<String, String[]> parameterMap = req.getParameterMap();

        // 硬编码获取方法参数
//        Object[] params = new Object[]{
//                req, resp, parameterMap.get("name")[0]
//        };

        // 实参列表要根据形参才能决定，首先拿到形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        // 定义实参列表
        Object[] params = new Object[parameterTypes.length];
        // 根据每个形参，获取实参
        for (int i = 0; i < params.length; i++) {
            // 形参类型
            Class<?> parameterType = parameterTypes[i];

            // 类型等于request
            if (parameterType == HttpServletRequest.class) {
                params[i] = req;
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                // 类型等于response
                params[i] = resp;
                continue;
            } else if (parameterType == String.class) {
                // 获取方法的参数注解，第一个纬度为索引，第二个维度为注解，二维数组的长度等于参数的长度
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();

                // 拿到第i个形参对应的注解
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof MeRequestParam) {
                        // 获取MeRequestParam的value，即自定义的实参name
                        String paramName = ((MeRequestParam) annotation).value();
                        // 根据注解的name值，从url参数中获取实参值
                        if (!"".equals(paramName.trim())) {
                            String value = Arrays.toString(parameterMap.get(paramName))
                                    // 去除 []
                                    .replaceAll("\\[|\\]", "")
                                    // 去除空白
                                    .replaceAll("\\s", "");
                            params[i] = value;
                        }
                    }
                }
            }
        }

        // 2.通过方法获取所在类实例
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        Object instance = ioc.get(beanName);

        // 3.通过反射调用方法
        method.invoke(instance, params);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 2. Ioc容器初始化
        ioc = new HashMap<>();

        // 3、扫描相关类
        doScanner(contextConfig.getProperty("scanPackage"));

        //==============IoC部分==============
        // 4、初始化扫描到的类，并放到IOC容器中
        doInstance();

        // AOP，新生成的代理对象

        //==============DI部分===============
        // 5、完成自动化的依赖注入
        doAutowired();

        //==============MVC部分==============
        // 6、初始化路径方法映射
        doInitHandlerMapping();

        System.out.println("Spring framework is init.");
    }

    /**
     * 1.1、加载配置文件
     *
     * @param contextConfigLocation 配置文件路径
     */
    private void doLoadConfig(String contextConfigLocation) {
        // 从类路径中找到配置文件所在路径
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            // 读取配置文件放到Properties对象中，及将配置从文件保存至内存中
            contextConfig.load(is);
            System.out.println("配置文件加载成功");
        } catch (IOException e) {
            System.err.println("未找到配置文件");
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 2、扫描相关的类
     *
     * @param scanPackage 被扫描的包名即com.coolingme.demo
     */
    private void doScanner(String scanPackage) {
        // 将 com.coolingme.demo 转为 文件路径，即把 . 换为 /
        String packageName = "/" + scanPackage.replaceAll("\\.", "/");
        // 通过类加载器获取class文件路径对象，file:/D:/workspace/workspace-learn/spring-1.0/target/classes/com/coolingme/demo
        URL url = this.getClass().getClassLoader().getResource(packageName);

        //classpath 下不仅有.class文件，还有.xml文件 .properties文件
        assert url != null;

        /**
         * 通过url的路径D:/workspace/workspace-learn/spring-1.0/target/classes/com/coolingme/demo
         * 找到被扫描的文件夹controller和service包，然后遍历
         */
        File classpath = new File(url.getFile());
        // 当成是一个ClassPath文件夹
        for (File file : Objects.requireNonNull(classpath.listFiles())) {
            if (file.isDirectory()) {
                // 递归扫描
                doScanner(scanPackage + "." + file.getName());
            } else {
                // 只添加Java类
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //全类名 = 包名.类名，比如：com.coolingme.demo.controller.DemoController
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNameList.add(className);
            }
        }
    }

    /**
     * 3、初始化扫描到的类，并放到IOC容器中
     * 工厂模式
     */
    private void doInstance() {
        if (classNameList.isEmpty()) {
            return;
        }
        try {
            // 循坏扫描的类
            for (String className : classNameList) {
                // 将Controller和Service需要放到IOC容器中，先获取类对象
                Class<?> clazz = Class.forName(className);

                // key为类名小写
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                // value为类实例对象
                Object instance = null;

                // 使用MeController注解
                if (clazz.isAnnotationPresent(MeController.class)) {
                    instance = clazz.newInstance();
                    // 放入IOC容器
                    ioc.put(beanName, instance);
                    System.out.println("IOC " + beanName + " : " + instance);
                } else if (clazz.isAnnotationPresent(MeService.class)) {
                    // 使用MeService注解
                    instance = clazz.newInstance();
                    // 如果有别名就使用别名
                    MeService meService = clazz.getAnnotation(MeService.class);
                    // 获取MeService注解的value的值
                    if (!"".equals(meService.value())) {
                        beanName = meService.value();
                    }

                    // 放入IOC容器
                    ioc.put(beanName, instance);
                    System.out.println("IOC " + beanName + " : " + instance);

                    // 获取父接口，默认一个（service的实现类和接口只能一对一），多了抛出异常
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> inter : interfaces) {
                        if (ioc.containsKey(inter.getName())) {
                            throw new Exception("The " + inter.getName() + "is exist!");
                        }
                        // 将接口也放入IOC容器，value为实现类对象
                        ioc.put(inter.getName(), instance);
                        System.out.println("IOC " + inter.getName() + " : " + instance);
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("IOC容器初始化失败");
            e.printStackTrace();
        }
    }

    /**
     * 4、完成自动化的依赖注入
     */
    private void doAutowired() {
        // 如果IOC容器为空，就不需要注入了
        if (ioc.isEmpty()) {
            return;
        }

        // 循环注入，获取键值对对象集合
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {

            // 把所有的包括private/protected/default/public 修饰字段都取出来
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                // 没有使用MeAutowired注释就不注入了
                if (!field.isAnnotationPresent(MeAutowired.class)) {
                    continue;
                }

                MeAutowired meAutowired = field.getAnnotation(MeAutowired.class);
                String beanName = meAutowired.value();

                // 如果没有定义beanName，就默认根据类型注入，
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }

                // 暴力访问
                field.setAccessible(true);

                // 反射赋值，第一个参数是对象，第二个参数是对象的字段值
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                    System.out.println("DI " + entry.getKey() + " => " + field.getName() + " : " + ioc.get(beanName));
                } catch (Exception e) {
                    System.err.println("依赖注入失败");
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 5、初始化HandlerMapping
     * 请求路径URL 和方法Method 一对一对应
     */
    private void doInitHandlerMapping() {
        // 容器为空，不需要映射
        if (ioc.isEmpty()) {
            return;
        }

        // 循环IOC容器
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 实例的类对象
            Class<?> clazz = entry.getValue().getClass();

            // 只获取Controller
            if (!clazz.isAnnotationPresent(MeController.class)) {
                continue;
            }

            // 获取类上的路径
            String classUrl = "";
            if (clazz.isAnnotationPresent(MeRequestMapping.class)) {
                classUrl = clazz.getAnnotation(MeRequestMapping.class).value();
            }

            // 默认获取public的方法
            for (Method method : clazz.getMethods()) {
                // 只获取有MeRequestMapping注解的方法
                if (!method.isAnnotationPresent(MeRequestMapping.class)) {
                    continue;
                }
                // 获取方法上的路径
                String methodUrl = method.getAnnotation(MeRequestMapping.class).value();
                // 拼接URL，去除重复的 / ,如 //user//get
                String url = ("/" + classUrl + "/" + methodUrl).replaceAll("/+", "/");

                // 存入映射关系容器中
                handlerMapping.put(url, method);
                System.out.println("Mapped " + entry.getKey() + " => " + url + " : " + method.getName());
            }
        }
    }

    /**
     * 将类名首字母小写
     *
     * @param simpleName 类名
     * @return 小写类名
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        // 且大写字母的ASCII码要小于小写字母的ASCII码32
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
